////////////////////////////////////////////////////////////////////////////
//	Renderer.h & Renderer.cpp
//		Singleton that initializes Direct3D and calls the render processes
//		for all objects that are passed to its Render method in a set.
//
//	Author:					Daniel Lima - August 2012
/////////////////////////////////////////////////////////////////////////////

#include "Renderer.h"

#include "RenderInstance.h"
#include "ShaderManager.h"
#include "TextureManager.h"
#include "MeshManager.h"

// Initialize the Renderer's static instance
Renderer* Renderer::sm_pInstance = nullptr;

// Default Constructor - Initialize all variables to safe values
Renderer::Renderer(void)
{
	m_pD3D			= nullptr;
	m_pDevice		= nullptr;
	m_nWindowWidth	= 0;
	m_nWindowHeight = 0;
}

// Used to access the singleton's instance
Renderer* Renderer::GetInstance(void)
{
	if(!sm_pInstance)
		sm_pInstance = new Renderer;

	return sm_pInstance;
}

// Used to delete the singleton's instance
void Renderer::DeleteInstance(void)
{
	if(sm_pInstance)
	{
		delete sm_pInstance;
		sm_pInstance = nullptr;
	}
}

// Initialize the graphic engines with the correct window parameters
int Renderer::Initialize(HWND hWindow, bool bWindowed, bool bVSync)
{
	RECT rect;
	GetWindowRect(hWindow, &rect);
	m_nWindowWidth = rect.right - rect.left;
	m_nWindowHeight = rect.bottom - rect.top;

	m_pD3D = Direct3DCreate9(D3D_SDK_VERSION);

	if(!m_pD3D) return E_FAIL;

	D3DPRESENT_PARAMETERS presentParams;

	ZeroMemory(&presentParams, sizeof(D3DPRESENT_PARAMETERS));
	presentParams.hDeviceWindow = hWindow;
	presentParams.Windowed = bWindowed;
	presentParams.PresentationInterval =
		bVSync ? D3DPRESENT_INTERVAL_ONE : D3DPRESENT_INTERVAL_IMMEDIATE;
	presentParams.SwapEffect = D3DSWAPEFFECT_DISCARD;

	HRESULT result = m_pD3D->CreateDevice(0, D3DDEVTYPE_HAL, hWindow,
		D3DCREATE_HARDWARE_VERTEXPROCESSING, &presentParams, &m_pDevice);

	if(result != D3D_OK)
		return result;

	UpdateWindow(hWindow);

	InitializeViewProj();

	SHADER_MNG->Initialize();
	TEXTURE_MNG->Initialize();

	return D3D_OK;
}

void Renderer::InitializeViewProj(void)
{
	// Initialize View Matrix
	D3DXVECTOR3 Eye(0.0f, 2.0f, -5.0f);
	D3DXVECTOR3 At(0.0f, 0.0f, 0.0f);
	D3DXVECTOR3 Up(0.0f, 1.0f, 0.0f);

	D3DXMatrixLookAtLH(&m_matrixView, &Eye, &At, &Up);
	
	// Initialize Projection Matrix
	float fieldOfView = D3DXToRadian(75);
	float aspect = m_nWindowWidth / (float)m_nWindowHeight;
	float zNear = 0.01f;
	float zFar = 100.0f;

	D3DXMatrixPerspectiveFovLH(&m_matrixProj, fieldOfView, aspect, zNear, zFar);
}

void Renderer::CameraRotate(float x, float y, float z)
{
	D3DXMATRIX rotate;
	D3DXMatrixRotationAxis(&rotate, &D3DXVECTOR3(1, 0, 0), D3DXToRadian(x));
	m_matrixView *= rotate;

	D3DXMatrixRotationAxis(&rotate, &D3DXVECTOR3(0, 1, 0), D3DXToRadian(y));
	m_matrixView *= rotate;

	D3DXMatrixRotationAxis(&rotate, &D3DXVECTOR3(0, 0, 1), D3DXToRadian(z));
	m_matrixView *= rotate;
}

void Renderer::CameraTranslate(float x, float y, float z)
{
	D3DXMATRIX translate;
	D3DXMatrixTranslation(&translate, x, y, z);
	m_matrixView *= translate;
}

int Renderer::AddRenderInstance(int shaderID, int textureID, int meshID)
{
	m_vpRenderInstances.push_back(new RenderInstance(shaderID, textureID, meshID));

	return m_vpRenderInstances.size() - 1;
}

// Renders a batch of RenderNode instances contained in the RenderSet passed in
void Renderer::Render(void)
{
	// Loop through all the instances the Renderer contains
	for(size_t i = 0; i < m_vpRenderInstances.size(); ++i)
	{
		// Get the current instance
		RenderInstance* pInst = m_vpRenderInstances[i];

		// If the pointer is null, there's something wrong
		if(!pInst)
			continue;

		// Set technique for the shader
		SHADER_MNG->SetTechnique(pInst->GetShader(), "myTechnique");

		// Begin the shader and get the number of passes
		UINT numPasses = SHADER_MNG->BeginShader(pInst->GetShader());

		// Loop through all the needed passes
		for(UINT uiPass = 0; uiPass < numPasses; ++uiPass)
		{
			// Begin the pass for the shader
			SHADER_MNG->BeginPass(pInst->GetShader(), uiPass);
			{
				// Set the shader uniforms
				SHADER_MNG->SetMatrix(pInst->GetShader(), "gWorld", &pInst->GetTransform());
				SHADER_MNG->SetMatrix(pInst->GetShader(), "gViewProjection", &(m_matrixView * m_matrixProj));
				SHADER_MNG->SetTexture(pInst->GetShader(), "gDiffuseTexture", TEXTURE_MNG->GetTexture(pInst->GetTexture()));
				SHADER_MNG->CommitChanges(pInst->GetShader());
			}
			// End the pass for the shader
			SHADER_MNG->EndPass(pInst->GetShader());
		}

		// Render the mesh
		MESH_MNG->DrawMesh(pInst->GetMesh());

		// End the shader
		SHADER_MNG->EndShader(pInst->GetShader());

		// This shouldn't be here, remove later (it's just making the instance rotate)
		pInst->Update();
	}
}

void Renderer::Shutdown(void)
{
}